package com.cy.mall.service.wechat.impl;

import com.cy.mall.comm.constant.CacheConstants;
import com.cy.mall.comm.utils.CommonUtils;
import com.cy.mall.comm.utils.JsonObjectMapper;
import com.cy.mall.comm.utils.SpringContextUtils;
import com.cy.mall.dao.mongodb.MongoDao;
import com.cy.mall.dao.mongodb.WechatMessageTemplateLogDao;
import com.cy.mall.model.PageBean;
import com.cy.mall.model.Result;
import com.cy.mall.model.doc.WechatMessageTemplateLog;
import com.cy.mall.model.domain.customer.CustomerWechat;
import com.cy.mall.model.dto.wechat.WechatMessageTemplateDTO;
import com.cy.mall.model.enums.wechat.WechatSopce;
import com.cy.mall.service.BaseService;
import com.cy.mall.service.customer.CustomerWechatService;
import com.cy.mall.service.wechat.WechatService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description 微信api服务
 * @Author tanyilin
 * @Date 2019/3/14 18:11
 */
@Slf4j
@Service
public class WechatServiceImpl extends BaseService implements WechatService {

    @Autowired
    private RedisTemplate               redisTemplate;
    @Autowired
    private RestTemplate                restTemplate;
    @Autowired
    private CustomerWechatService       customerWechatService;
    @Autowired
    private WechatMessageTemplateLogDao wechatMessageTemplateLogDao;
    @Autowired
    private MongoDao                    mongoDao;

    /** appId */
    @Value("${wechat.app-id}")
    private String appId;

    /** secret */
    @Value("${wechat.secret}")
    private String secret;

    /** 获取基础 access-token 的url */
    @Value("${wecaht.access-token.url}")
    private String accessTokenUrl;

    /** 获取微信分享 ticket 的url */
    @Value("${wechat.ticket.url}")
    private String ticketUrl;

    /** 用户授权的url */
    @Value("${wechat.oauth2.authorize.url}")
    private String authorizeUrl;

    /** 获取用户 access-token 的url */
    @Value("${wechat.oauth2.user-access-token.url}")
    private String userAccessTokenUrl;

    /** 获取微信用户信息的url */
    @Value("${wechat.oauth2.user-info.url}")
    private String userInfoUrl;

    /** 用户授权后回调的绑定微信关系的入口url */
    @Value("${wechat.oauth2.bind.callback.url}")
    private String bindCallbackUrl;

    /** 发送微信消息模板的url */
    @Value("${wechat.message.template.send.url}")
    private String messageTemplateSendUrl;

    /** 微信分享参数 */
    private final String shareKey = "jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s";

    /** redis key */
    private final String authKey = "WECHAT_AUTH_KEY:";


    @Override
    @Cacheable(value = CacheConstants.CACHE_VALUE_1H)
    public String getBaseAccessToken() {
        //组装参数
        Map<String, String> params = new HashMap<>();
        params.put("appid", appId);
        params.put("secret", secret);

        //发送请求获取token
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(accessTokenUrl, String.class, params);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "获取TOKEN失败");
        Map<String, String> map = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        log.info("请求微信access_token回调: {}", map);
        String token = map.get("access_token");
        state(StringUtils.hasLength(token), "获取TOKEN失败");
        return token;
    }

    @Override
    @Cacheable(value = CacheConstants.CACHE_VALUE_1H)
    public String getTicket(String token) {
        //组装参数
        Map<String, String> params = new HashMap<>();
        params.put("token", token);

        //发送请求获取token
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(ticketUrl, String.class, params);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "获取TICKET失败");
        Map<String, String> map = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        log.info("请求ticket回调: {}", map);
        String ticket = map.get("ticket");
        state(StringUtils.hasLength(ticket), "获取TICKET失败");
        return ticket;
    }

    @Override
    public Map<String, String> getShareSignature(String token, String ticket, String url) {
        String noncestr = UUID.randomUUID().toString();
        String timestamp = System.currentTimeMillis() + "";
        timestamp = timestamp.substring(0, 10);

        String sign = String.format(shareKey, ticket, noncestr, timestamp, url);
        String signature = DigestUtils.sha1Hex(sign);
        Map<String, String> map = new HashMap<>();
        map.put("appId", appId);
        map.put("timestamp", timestamp);
        map.put("nonceStr", noncestr);
        map.put("signature", signature);
        map.put("url", url);
        log.info("微信分享签名====================》 token  =  {}", token);
        log.info("微信分享签名====================》 ticket =  {}", ticket);
        log.info("微信分享签名====================》 {}", map);
        return map;
    }

    @Override
    public String getAuthorizeUrl(Long customerId, WechatSopce wechatSopce) {

        //生成随机key
        String state = UUID.randomUUID().toString();
//        try {
//            bindCallbackUrl = URLEncoder.encode(bindCallbackUrl, "UTF-8");
//        } catch (UnsupportedEncodingException e) {
//            log.error("回调地址 bindCallbackUrl encode 异常", e);
//        }
        String url = String.format(authorizeUrl, appId, bindCallbackUrl, wechatSopce.getSopce(), state);

        Map<String, String> map = new HashMap<String, String>();
        map.put("customerId", customerId.toString());
        map.put("wechatSopce", wechatSopce.name());

        String key = authKey.concat(state);

        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, 4, TimeUnit.MINUTES);

        log.info("用户微信授权地址  url = {} ", url);

        return url;
    }

    @Override
    public Map<String, Object> getUserAccessToken(String code) {

        //组装参数
        Map<String, String> params = new HashMap<>();
        params.put("appid", appId);
        params.put("secret", secret);
        params.put("code", code);

        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(userAccessTokenUrl, String.class, params);

        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "请求微信用户的ACCESSTOKEN失败");
        log.info("获取微信用户ACCESSTOKEN回调: {}", responseEntity.getBody());

        Map<String, Object> body = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        Object errorCode = body.get("errcode");
        state(errorCode == null, "获取微信用户的ACCESSTOKEN失败");

        return body;
    }

    @Override
    public Map<String, Object> getUserInfo(String accessToken, String openId) {

        //组装参数
        Map<String, String> params = new HashMap<>();
        params.put("access_token", accessToken);
        params.put("openid", openId);

        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(userInfoUrl, String.class, params);

        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "请求获取微信用户信息失败");
        log.info("请求获取微信用户信息回调: {}", responseEntity.getBody());

        Map<String, Object> body = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        Object errorCode = body.get("errcode");
        state(errorCode == null, "获取微信用户信息失败");

        return body;
    }

    @Override
    public CustomerWechat bindCallback(String code, String state) {

        //验证缓存是否过期
        String key = authKey.concat(state);
        Map<String, String> map = redisTemplate.opsForHash().entries(key);
        state(!CommonUtils.isEmpty(map), "验证超时，请重新操作");

        //获得缓存中的信息
        Long customerId = Long.valueOf(map.get("customerId"));
        WechatSopce wechatSopce = WechatSopce.get(map.get("wechatSopce"));
        log.info("缓存中的信息：{} ", map);

        //获取微信用户accessToken
        Map<String, Object> body = getUserAccessToken(code);
        String accessToken = body.get("access_token").toString();
        String openId = body.get("openid").toString();
        log.info("微信用户accessToken：{} ", body);

        //获取用户用户信息
        Object unionId = body.get("unionid");
        Object nickName = body.get("nickname");
        Object headImg = body.get("headimgurl");

        if(WechatSopce.USER_INFO.equals(wechatSopce)) {
            //获取微信用户信息
            body = getUserInfo(accessToken, openId);
            log.info("微信用户信息：{} ", body);
            unionId = body.get("unionid");
            nickName = body.get("nickname");
            headImg = body.get("headimgurl");
        }

        CustomerWechat customerWechat = new CustomerWechat();
        customerWechat.setCustomerId(customerId);
        customerWechat.setOpenId(openId);
        customerWechat.setUnionId(unionId == null ? null : String.valueOf(unionId));
        customerWechat.setNickName(nickName == null ? null : String.valueOf(nickName));
        customerWechat.setHeadImg(headImg == null ? null : String.valueOf(headImg));
        customerWechat.setManualBind(WechatSopce.USER_INFO.equals(wechatSopce));

        return customerWechat;
    }

    @Override
    public void bindCustomerWechat(CustomerWechat customerWechat) {

        Long customerId = customerWechat.getCustomerId();
        String openId = customerWechat.getOpenId();

        //查询openID 是否绑定
        CustomerWechat openIdDO = customerWechatService.selectByOpenId(openId);
        if(!customerWechat.getManualBind() && openIdDO != null && openIdDO.getManualBind()) {
            //当前操作为自动绑定，但openId已被手动绑定
            log.info("客户ID[{}],openId[{}],openId已被手动绑定,无法自动更新绑定关系。",customerId, openId);
            return;
        }

        //查询 customerId 该用户是否绑定
        CustomerWechat customerIdDO = customerWechatService.selectByCustomerId(customerId);
        if(!customerWechat.getManualBind() && customerIdDO != null && customerIdDO.getManualBind()) {
            //当前操作为自动绑定，但 customerId 已被手动绑定
            log.info("客户ID[{}],openId[{}],customerId 已被手动绑定,无法自动更新绑定关系。", customerId, openId);
            return;
        }

        if(openIdDO != null && customerIdDO != null && openIdDO.getId().equals(customerIdDO.getId())) {
            //openIdDO、customerIdDO 均已绑定，且同一个，则更新
            customerWechat.setId(openIdDO.getId());
            customerWechatService.updateCustomerWechat(customerWechat);
            return;
        }

        if(openIdDO != null && customerIdDO == null) {
            // openIdDO 已绑定，customerIdDO 未绑定
            customerWechat.setId(openIdDO.getId());
            customerWechatService.updateCustomerWechat(customerWechat);
            return;
        }

        if(customerIdDO != null && openIdDO == null) {
            //customerIdDO 已绑定，openIdDO 未绑定
            customerWechat.setId(customerIdDO.getId());
            customerWechatService.updateCustomerWechat(customerWechat);
            return;
        }

        //以上情况都不是，则新增 客户-微信关联关系 customerWechat
        customerWechatService.insertCustomerWechat(customerWechat);

    }

    @Override
    public Result sendTemplateMessage(WechatMessageTemplateDTO wechatMessageTemplateDTO) {

        String openId = wechatMessageTemplateDTO.getOpneId();
        String json = JsonObjectMapper.toJson(wechatMessageTemplateDTO);

        //记录微信消息发送日志
        WechatMessageTemplateLog requestLog = new WechatMessageTemplateLog();
        requestLog.setOpenId(wechatMessageTemplateDTO.getOpneId());
        requestLog.setRequestData(json);
        requestLog.setCreateTime(new Date());
        wechatMessageTemplateLogDao.insert(requestLog);

        //发送微信模板消息
        log.info("给用户【{}】发送微信消息模版请求报文 requestData： {}", openId, json);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
        //获取微信accessToken
        String accessToken = SpringContextUtils.getBean(WechatService.class).getBaseAccessToken();
        log.info("发送微信消息模版的 accessToken = {} ", accessToken);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(messageTemplateSendUrl, httpEntity, String.class, accessToken);

        if(!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
            return Result.fail("发送微信消息模版请求失败");
        }

        log.info("给用户【{}】发送微信消息模版回调报文 responseData： {}", openId, responseEntity.getBody());

        //保存回调记录
        WechatMessageTemplateLog responseLog = new WechatMessageTemplateLog();
        responseLog.setId(requestLog.getId());
        responseLog.setResponseData(responseEntity.getBody());
        responseLog.setUpdateTime(new Date());
        mongoDao.update(responseLog);

        String responseBody = responseEntity.getBody();
        Map<String, Object> result = JsonObjectMapper.read(responseBody, Map.class);
        Integer errcode = (Integer) result.get("errcode");
        if (!Integer.valueOf(0).equals(errcode)) {
            return Result.fail("发送微信消息模版失败");
        }

        return Result.success();
    }

    @Override
    public PageBean<WechatMessageTemplateLog> getWechatMessageTemplateSendLog(String openId, int page, int limit) {

        if(CommonUtils.isEmpty(page)) {
            page = 1;
        }
        if(CommonUtils.isEmpty(limit)) {
            limit = 10;
        }
        page = page - 1;
        Pageable pageable = new PageRequest(page, limit, Sort.Direction.DESC, "updateTime");
        Page<WechatMessageTemplateLog> pageModel = null;
        if(CommonUtils.isEmpty(openId)) {
            pageModel = wechatMessageTemplateLogDao.findAll(pageable);
        }else {
            pageModel = wechatMessageTemplateLogDao.findAllByOpenId(openId, pageable);
        }

        List<WechatMessageTemplateLog> list = pageModel.getContent();
        int total = (int) pageModel.getTotalElements();

        return new PageBean<>(list, total);
    }
}
